﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using IronCow;

namespace Milkify.Data
{
    public class CompositeTask : INotifyPropertyChanged
    {
        #region Private Members
        private Task[] mTasks;
        private Dictionary<string, object> mSetProperties = new Dictionary<string, object>();
        #endregion

        #region Getter and Setters Implementation
        private T GetPropertyValue<T>(string propertyName) where T : class
        {
            return GetPropertyValue<T>(propertyName, null);
        }

        private T GetPropertyValue<T>(string propertyName, T defaultValue) where T : class
        {
            var propertyInfo = typeof(Task).GetProperty(propertyName);
            var value = (T)propertyInfo.GetValue(mTasks[0], null);
            for (int i = 1; i < mTasks.Length; i++)
            {
                var otherValue = (T)propertyInfo.GetValue(mTasks[i], null);
                if (!object.Equals(value, otherValue))
                    return defaultValue;
            }
            return value;
        }

        private void SetPropertyValue<T>(string propertyName, T value)
        {
            SetPropertyValue<T>(propertyName, value, true);
        }

        private void SetPropertyValue<T>(string propertyName, T value, bool skipIfNull)
        {
            if (value != null || !skipIfNull)
            {
                if (mSetProperties.ContainsKey(propertyName))
                    mSetProperties[propertyName] = value;
                else
                    mSetProperties.Add(propertyName, value);

                OnPropertyChanged(propertyName);
            }
        }

        private Nullable<T> GetNullablePropertyValue<T>(string propertyName) where T : struct
        {
            var propertyInfo = typeof(Task).GetProperty(propertyName);
            var value = (T)propertyInfo.GetValue(mTasks[0], null);
            for (int i = 1; i < mTasks.Length; i++)
            {
                var otherValue = (T)propertyInfo.GetValue(mTasks[i], null);
                if (!value.Equals(otherValue))
                    return null;
            }
            return value;
        }

        private void SetNullablePropertyValue<T>(string propertyName, Nullable<T> value) where T : struct
        {
            SetNullablePropertyValue<T>(propertyName, value, true);
        }

        private void SetNullablePropertyValue<T>(string propertyName, Nullable<T> value, bool skipIfHasNoValue) where T : struct
        {
            if (value.HasValue || !skipIfHasNoValue)
            {
                object actualValue = value.HasValue ? value.Value : default(T);
                if (mSetProperties.ContainsKey(propertyName))
                    mSetProperties[propertyName] = actualValue;
                else
                    mSetProperties.Add(propertyName, actualValue);

                OnPropertyChanged(propertyName);
            }
        }
        #endregion

        #region Public Properties
        public DateTime? Added
        {
            get { return GetNullablePropertyValue<DateTime>("Added"); }
        }

        public DateTime? Completed
        {
            get { return GetNullablePropertyValue<DateTime>("Completed"); }
        }

        public DateTime? Created
        {
            get { return GetNullablePropertyValue<DateTime>("Created"); }
        }

        public DateTime? Deleted
        {
            get { return GetNullablePropertyValue<DateTime>("Deleted"); }
        }

        public string Due
        {
            get { return GetPropertyValue<string>("Due"); }
            set
            {
                SetPropertyValue<string>("Due", value);
                OnPropertyChanged("DueDateTime");
                OnPropertyChanged("HasDueTime");
                OnPropertyChanged("IsLate");
            }
        }

        public DateTime? DueDateTime
        {
            get { return GetNullablePropertyValue<DateTime>("DueDateTime"); }
            set
            {
                SetNullablePropertyValue<DateTime>("DueDateTime", value);
                OnPropertyChanged("Due");
                OnPropertyChanged("HasDueTime");
                OnPropertyChanged("IsLate");
            }
        }

        public string Estimate
        {
            get { return GetPropertyValue<string>("Estimate"); }
            set { SetPropertyValue<string>("Estimate", value); }
        }

        public bool? HasDueTime
        {
            get { return GetNullablePropertyValue<bool>("HasDueTime"); }
        }

        public bool? IsCompleted
        {
            get { return GetNullablePropertyValue<bool>("IsCompleted"); }
        }

        public bool? IsDeleted
        {
            get { return GetNullablePropertyValue<bool>("IsDeleted"); }
        }

        public bool? IsLate
        {
            get { return GetNullablePropertyValue<bool>("IsLate"); }
        }

        public bool? IsIncomplete
        {
            get { return GetNullablePropertyValue<bool>("IsIncomplete"); }
        }

        public Location Location
        {
            get { return GetPropertyValue<Location>("Location"); }
            set
            {
                SetPropertyValue<Location>("Location", value, false);
                OnPropertyChanged("LocationName");
            }
        }

        public string LocationName
        {
            get { return GetPropertyValue<string>("LocationName"); }
        }

        public DateTime? Modified
        {
            get { return GetNullablePropertyValue<DateTime>("Modified"); }
        }

        public string Name
        {
            get { return GetPropertyValue<string>("Name"); }
            set { SetPropertyValue<string>("Name", value); }
        }

        public TaskTaskNoteCollection Notes
        {
            get { return null; }
        }

        public TaskList Parent
        {
            get { return GetPropertyValue<TaskList>("Parent"); }
            set
            {
                SetPropertyValue<TaskList>("Parent", value);
                OnPropertyChanged("ParentName");
            }
        }

        public string ParentName
        {
            get { return GetPropertyValue<string>("ParentName"); }
        }

        public int? Postponed
        {
            get { return GetNullablePropertyValue<int>("Postponed"); }
        }

        public TaskPriority? Priority
        {
            get { return GetNullablePropertyValue<TaskPriority>("Priority"); }
            set
            {
                SetNullablePropertyValue<TaskPriority>("Priority", value.Value);
            }
        }

        public string Recurrence
        {
            get { return GetPropertyValue<string>("Recurrence"); }
            set { SetPropertyValue<string>("Recurrence", value); }
        }

        public int? SeriesId
        {
            get { return GetNullablePropertyValue<int>("SeriesId"); }
        }

        public string Source
        {
            get { return GetPropertyValue<string>("Source"); }
        }

        public string Url
        {
            get { return GetPropertyValue<string>("Url"); }
            set { SetPropertyValue<string>("Url", value); }
        } 
        #endregion

        #region Construction
        public CompositeTask(Task[] tasks)
        {
            if (tasks.Length == 0)
                throw new ArgumentException("CompositeTask needs at least one task.");
            mTasks = new Task[tasks.Length];
            tasks.CopyTo(mTasks, 0);
        } 
        #endregion

        #region Public Methods
        public void Complete()
        {
            foreach (var task in mTasks)
            {
                task.Complete();
            }
        }

        public void Delete()
        {
            foreach (var task in mTasks)
            {
                task.Delete();
            }
        }

        public string GetTags(string separator)
        {
            var firstTags = mTasks[0].GetTags(separator);
            for (int i = 1; i < mTasks.Length; i++)
            {
                var otherTags = mTasks[i].GetTags(separator);
                if (firstTags != otherTags)
                    return null;
            }
            return firstTags;
        }

        public void Postpone()
        {
            foreach (var task in mTasks)
            {
                task.Postpone();
            }
        }

        public void SetTags(IEnumerable<string> tags)
        {
            foreach (var task in mTasks)
            {
                task.SetTags(tags);
            }
        }

        public void SetTags(string formattedTags, char[] separators)
        {
            foreach (var task in mTasks)
            {
                task.SetTags(formattedTags, separators);
            }
        }

        public void Uncomplete()
        {
            foreach (var task in mTasks)
            {
                task.Uncomplete();
            }
        } 
        #endregion

        #region Editing Methods
        public void CommitSetPropertiesAsync()
        {
            var action = (Action)CommitSetProperties;
            action.BeginInvoke(CommitSetPropertiesAsyncCallback, action);
        }

        private void CommitSetPropertiesAsyncCallback(IAsyncResult ar)
        {
            try
            {
                var action = (Action)ar.AsyncState;
                action.EndInvoke(ar);
            }
            catch (Exception e)
            {
                // Raise this exception on the UI thread so it's picked up
                // by the application exception handler.
                App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() => { throw e; }));
            }
        }

        public void CommitSetProperties()
        {
            foreach (string propertyName in mSetProperties.Keys)
            {
                var value = mSetProperties[propertyName];
                var propertyInfo = typeof(Task).GetProperty(propertyName);
                foreach (var task in mTasks)
                {
                    propertyInfo.SetValue(task, value, null);
                }

                Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Set property {0} to {1} on {2} tasks.", propertyName, value, mTasks.Length);
            }

            mSetProperties.Clear();
        }
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                if (App.Current.Dispatcher != null)
                    App.Current.Dispatcher.Invoke(new Action(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName))));
                else
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}
